package logic

import (
	"os"
	"time"

	"github.com/asdine/storm/v3"
	"github.com/asdine/storm/v3/codec/msgpack"
	"go.etcd.io/bbolt"
)

type UnitType int

const (
	ProcessType UnitType = iota
	DockerType
)

type UnitStatus int

const (
	RunningStatus UnitStatus = iota
	RollbackStatus
	ErrorStatus
)

type UnitInfo struct {
	WorkDir    string
	Env        map[string]string
	Command    string
	Args       string
	IsOrigin   bool // 移除后，需要删除workDir
	IsDownload bool // 移除后，需要删除文件
}

type Unit struct {
	Name          string `storm:"id"` // this field will be indexed
	Pid           int
	RestartNumber int
	RecoverNumber int
	Type          UnitType
	Info          UnitInfo
	Status        UnitStatus
	Target        string
	PrevTarget    string
	CreatedAt     time.Time
	StartAt       time.Time
}

func (unit *Unit) Container() UnitContainer {
	switch unit.Type {
	case ProcessType:
		return &Process{Unit: unit}
	case DockerType:
		return &Docker{Unit: unit}
	}
	return nil
}

type DB struct {
	file string
	*storm.DB
	opts []func(options *storm.Options) error
}

func InitDatabase(file string, readOnly bool) (*DB, error) {
	mode := os.FileMode(0600)
	if readOnly {
		mode = 0400
	}
	db := &DB{
		file: file,
		opts: []func(options *storm.Options) error{storm.Codec(msgpack.Codec), storm.BoltOptions(mode, &bbolt.Options{ReadOnly: readOnly})},
	}
	return db, nil
}

func (db *DB) Close() error {
	if db.DB == nil {
		return nil
	}
	err := db.DB.Close()
	db.DB = nil
	return err
}

func (db *DB) reopen() error {
	if db.DB != nil {
		return nil
	}
	var err error
	db.DB, err = storm.Open(db.file, db.opts...)
	if err != nil {
		return err
	}
	return nil
}

func (db *DB) LoadAllUnit() ([]Unit, error) {
	if err := db.reopen(); err != nil {
		return nil, err
	}
	defer db.Close()
	var units []Unit
	err := db.All(&units)
	return units, err
}

func (db *DB) FindUnit(name string) (*Unit, error) {
	if err := db.reopen(); err != nil {
		return nil, err
	}
	defer db.Close()
	var unit Unit
	if err := db.One("Name", name, &unit); err != nil {
		return nil, err
	}
	return &unit, nil
}

func (db *DB) AddUnit(unit *Unit) error {
	if err := db.reopen(); err != nil {
		return err
	}
	defer db.Close()
	return db.Save(unit)
}

func (db *DB) SaveUnit(unit *Unit) error {
	if err := db.reopen(); err != nil {
		return err
	}
	defer db.Close()
	return db.Save(unit)
}

func (db *DB) DeleteUnit(unit *Unit) error {
	if err := db.reopen(); err != nil {
		return err
	}
	defer db.Close()
	return db.DeleteStruct(unit)
}
